/* 插头DP

* 1.回路性质
    性质1：两两配对
    性质2：路径间不可能交叉

* 2.记录连通块
    (1)最小表示法(坐标表示法)
        从左向右遍历所有边，从小到大依次标号遍历到的未标号连通块
    (2)括号表示法
        有一个边上去，必有一边下来，因此每一对配对的括号，是一条回路
        

* 3.状态转移
    状态表示
        f[i][j][S]表示当前递推到 (i,j)，
        且递推完的格子和没有递推的格子之间的分界线的状态是S的哈密顿回路的方案数

    设当前递推到的格子是 (i,j)，
    令 (i,j) 左边这条边的状态是x，
            上面这条边的状态是y，
            下面这条边的状态是z，
            右边这条边的状态是w，
            设当前分界线的状态是state
    0无插头 1回路左端点(出插头) 2回路右端点(入插头)

    case1: !x&&!y
            (1) 不用(i, j) w
            (2)   用(i, j) w+g(i, j)


    case2:!x&& y w+g(i, j)

    case3: x&&!y w+g(i, j)

    case4: x==y==1 w+g(i, j)

    case5: x==y==2 w+g(i, j)

    case6: x==2 y==1 w+g(i, j)

    case7: x==1 y==2 w+g(i, j) 更新答案

* 数据规模
    状态S=4^13,需要手写哈希表存储
    当状态数量较多时，需要滚动数组处理
    如果TLE，可以考虑预处理状态
*/




#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define DEBUG
inline int read()
{
	int x=0, f=1;char c=getchar();
    if(c=='-') f*=-1;
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48,c=getchar();
	return x;
}
inline void write(int x)
{
	if (x>=10) write(x/10);
	putchar(x%10+48);
}//快读快输
const int N = 130, M = 301; //状态

int n, m;
int g[N][N]; //地图
int q[2][N]; //q 滚动数组 每次滚动的有效状态在哈希表中的下标
int cnt[2]; //每次滚动的有效状态的数量
int hsh[2][M]; //hsh 滚动哈希表
int v[2][M]; //v 表示哈希表中每个状态对应的方案数

void init()
{
    n=read(), m=read();
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
            scanf("%d", &g[i][j]);
        
    memset(hsh, -1, sizeof hsh);
}

int find(int cur, int x) //开放寻址法(模板)找x的位置，cur表示当前滚动的位置
{
    int pos=x%M;
    while(hsh[cur][pos]!=-1 && hsh[cur][pos]!=x)
        if(++pos==M)
            pos=0;
    return pos;
}

void insert(int cur, int state, int w) //将状态插入到哈希表中，w表示state的方案数，cur为滚动数组维数
{
    int t=find(cur, state);
    if(hsh[cur][t]==-1) //哈希表不存在，新状态
    {
        hsh[cur][t]=state, v[cur][t]=w; //插入哈希表
        q[cur][++cnt[cur]]=t; //插入队列
    }
    else v[cur][t]=max(v[cur][t], w); //已经在哈希表中，更新方案数
}   

int get(int state, int k) //第k个格子状态,四进制第k位数字
{
    return state>>k*2&3;
}

int set(int k, int v) //构造四进制的第k位数字是v的数
{
    return v*(1<<k*2);
}

void solve()
{
    init();

    int res=-0x3f3f3f3f;
    int cur=0; //当前滚动数组用到第几维
    insert(cur, 0, 0); //最开始无边，状态0 方案0
    for(int i=1; i<=n; i++)
    {
        //每次枚举一行，将行末的所有状态转化为行首的状态
        for(int j = 1; j <= cnt[cur]; j++)
            hsh[cur][q[cur][j]] <<= 2; //四进制下存一位，相当于二进制下存两位

        for(int j=1; j<=m; j++)
        {
            int last=cur; //记录当前行的状态时滚动数组中第几维
            cur^=1, cnt[cur]=0;
            memset(hsh[cur], -1, sizeof hsh[cur]);
            for(int k=1; k<=cnt[last]; k++) //枚举当前这一层的有效状态
            {
                int state =hsh[last][q[last][k]]; //记录当前状态
                int w=v[last][q[last][k]];
                int x=get(state, j-1), y=get(state, j); //记录当前格子的左边和上面的状态
                
                if(!x && !y) //状态1
                {
                    insert(cur, state, w);
                   if(i<n && j<m)
                        insert(cur, state+set(j-1,1) + set(j,2), w+g[i][j]);
                }
                else if(x &&!y) //状态2
                {
                    if(j<m) insert(cur, state-set(j-1, x)+set(j, x), w+g[i][j]);
                    if(i<n) insert(cur, state, w+g[i][j]);
                }
                else if(!x && y) //状态3
                {
                    if(j<m) insert(cur, state, w+g[i][j]);
                    if(i<n) insert(cur, state+set(j-1, y)-set(j, y), w+g[i][j]);
                }
                else if(x==1 && y==1) //状态4
                {
                    //向右找到第一个右端点变成1
                    for(int u=j+1, s=1; ; u++) //s:左端点数量
                    {
                        int z=get(state, u);
                        if(z==1) s++; //遇到左端点，数量+1
                        else if(z==2) 
                        {
                            if(--s==0) //找到了与当前两个左端点匹配的右端点
                            {
                                insert(cur, state-set(j-1, x)-set(j, y)-set(u, 1), w+g[i][j]);
                                break;
                            }
                        }
                    }
                }
                else if(x==2 && y==2) //状态5
                {
                    //往左找到第一个左端点变成2
                    for(int u=j-2, s=1; ; u--) //s:右端点数量
                    {
                        int z=get(state, u);
                        if(z==2) s++; //遇到右端点，数量+1
                        else if(z==1) 
                        {
                            if(--s==0) //与当前两个右端点匹配的左端点
                            {
                                insert(cur, state-set(j-1, x)-set(j, y)+set(u, 1), w+g[i][j]);
                                break;
                            }
                        }
                    }  
                }
                else if(x==2 && y==1) //状态6
                {
                    insert(cur, state-set(j-1, x)-set(j, y), w+g[i][j]);
                }
                else if(x==1 && y==2 && state-set(j-1, x)-set(j, y)==0) //状态7
                    res=max(res, w+g[i][j]);
            }
        }
    }
    printf("%d\n", res);
}

signed main()
{
    #ifdef DEBUG
        freopen("./in.txt","r",stdin);
        freopen("./out.txt","w",stdout);
    #endif

    
    int T=1; //T=read();
    while(T--)
    {
        solve();
    }
    return 0;
}